package org.truenewx.tnxjee.webmvc.http.converter;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.method.HandlerMethod;
import org.truenewx.tnxjee.core.jackson.JsonWithClassProperty;
import org.truenewx.tnxjee.core.jackson.TypedPropertyFilter;
import org.truenewx.tnxjee.core.util.BeanUtil;
import org.truenewx.tnxjee.core.util.ClassUtil;
import org.truenewx.tnxjee.core.util.JacksonUtil;
import org.truenewx.tnxjee.core.util.PropertyMeta;
import org.truenewx.tnxjee.web.context.SpringWebContext;
import org.truenewx.tnxjee.webmvc.http.annotation.ResultFilter;
import org.truenewx.tnxjee.webmvc.jackson.AttachFieldBeanSerializerModifier;
import org.truenewx.tnxjee.webmvc.method.support.HandlerMethodMapping;
import org.truenewx.tnxjee.webmvc.util.WebMvcUtil;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 基于Jackson实现的HTTP消息JSON转换器
 */
public class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private final ApplicationContext context;
    private final HandlerMethodMapping handlerMethodMapping;

    private final Map<String, ObjectMapper> mappers = new HashMap<>();
    private final ObjectMapper defaultInternalMapper = JacksonUtil.copyDefaultMapper();
    private final ObjectMapper defaultExternalMapper = JacksonUtil.copyDefaultMapper();

    public JacksonHttpMessageConverter(ApplicationContext context, HandlerMethodMapping handlerMethodMapping) {
        super(JacksonUtil.copyNonConcreteClassedMapper()); // 默认映射器实际上作为读取器，针对非具化类型读取类型字段
        setDefaultCharset(StandardCharsets.UTF_8);
        this.context = context;
        this.handlerMethodMapping = handlerMethodMapping;
        // 默认外部映射器需要附加字段输出能力
        withSerializerModifier(this.defaultExternalMapper, new AttachFieldBeanSerializerModifier(context));
    }

    private void withSerializerModifier(ObjectMapper mapper, BeanSerializerModifier modifier) {
        mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(modifier));
    }

    private String getMapperKey(boolean internal, Method method) {
        return (internal ? "in:" : "ex:") + method.toString();
    }

    @Override
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        HttpServletRequest request = SpringWebContext.getRequest();
        boolean internal = !(outputMessage instanceof ServerHttpResponse) // RPC请求端视为内部
                || (request != null && WebMvcUtil.isInternalRpc(request));
        Method method = null;
        if (request != null) {
            HandlerMethod handlerMethod = this.handlerMethodMapping.getHandlerMethod(request);
            if (handlerMethod != null) {
                method = handlerMethod.getMethod();
            }
        }
        ObjectMapper mapper = null;
        if (method != null) {
            mapper = getMapper(internal, method);
        } else if (internal) {
            if (type instanceof ParameterizedType pt) {
                if (JacksonUtil.isNonConcrete((Class<?>) pt.getRawType())) {
                    mapper = JacksonUtil.withClassProperty(JacksonUtil.copyDefaultMapper(), ClassUtil::isComplex);
                }
            }
        }
        if (mapper != null) {
            if (internal) { // 内部请求需考虑代理对象的处理
                object = getUltimateObject(object);
            }
            String json = mapper.writeValueAsString(object);
            Charset charset = Objects.requireNonNullElse(getDefaultCharset(), StandardCharsets.UTF_8);
            IOUtils.write(json, outputMessage.getBody(), charset.name());
            return;
        }

        super.writeInternal(object, type, outputMessage);
    }

    private ObjectMapper getMapper(boolean internal, Method method) {
        String mapperKey = getMapperKey(internal, method);
        ObjectMapper mapper = this.mappers.get(mapperKey);
        if (mapper == null) {
            // 存在结果过滤设置，或需要生成对象类型字段，则需构建方法特定的映射器
            ResultFilter[] resultFilters = method.getAnnotationsByType(ResultFilter.class);
            boolean classPropertyNeeded = isClassPropertyNeeded(internal, method);
            if (ArrayUtils.isNotEmpty(resultFilters) || classPropertyNeeded) {
                mapper = buildMapper(internal, method.getReturnType(), resultFilters, classPropertyNeeded);
                this.mappers.put(mapperKey, mapper);
            } else { // 取默认映射器
                mapper = internal ? this.defaultInternalMapper : this.defaultExternalMapper;
            }
        }
        return mapper;
    }

    private boolean isClassPropertyNeeded(boolean internal, Method method) {
        if (internal) { // 内部调用才可能需要构建输出类型字段的映射器
            // 方法上有@JsonWithClassProperty注解，则一定输出类型字段
            if (method.getAnnotation(JsonWithClassProperty.class) != null) {
                return true;
            }
            Class<?> returnType = method.getReturnType();
            if (JacksonUtil.isNonConcrete(returnType)) {
                return true;
            }
            Collection<PropertyMeta> metas = ClassUtil.findPropertyMetas(returnType, true, false, true, null);
            for (PropertyMeta meta : metas) {
                if (!meta.containsAnnotation(JsonIgnore.class)) {
                    // 需要序列化的属性标注了@JsonWithClassProperty注解，则一定输出类型字段
                    if (meta.containsAnnotation(JsonWithClassProperty.class)) {
                        return true;
                    }
                    Class<?> type = meta.getType();
                    if (JacksonUtil.isNonConcrete(type)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private ObjectMapper buildMapper(boolean internal, Class<?> resultType, ResultFilter[] resultFilters,
            boolean classPropertyNeeded) {
        TypedPropertyFilter filter = new TypedPropertyFilter();
        AttachFieldBeanSerializerModifier attachFieldModifier = new AttachFieldBeanSerializerModifier(this.context);
        for (ResultFilter resultFilter : resultFilters) {
            Class<?> filteredType = resultFilter.type();
            if (filteredType == Object.class) {
                filteredType = resultType;
            }
            filter.addIncludedProperties(filteredType, resultFilter.included());
            filter.addExcludedProperties(filteredType, resultFilter.excluded());
            attachFieldModifier.addResultEnumFilters(filteredType, resultFilter.enums());
        }
        // 被过滤的类型中如果不包含结果类型，则加入结果类型，以确保至少包含结果类型
        Class<?>[] filteredTypes = filter.getTypes();
        if (!ArrayUtils.contains(filteredTypes, resultType)) {
            filteredTypes = ArrayUtils.add(filteredTypes, resultType);
        }
        ObjectMapper mapper = internal ? this.defaultInternalMapper.copy() : this.defaultExternalMapper.copy();
        JacksonUtil.withPropertyFilter(filter, mapper, filteredTypes);
        if (classPropertyNeeded) { // 需要附加类型属性，则针对所有复合类型均附加类型属性输出能力，以适配集合属性中的复合类型元素
            JacksonUtil.withComplexClassProperty(mapper);
        }
        return mapper;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private <T> T getUltimateObject(T candidate) {
        if (candidate != null) {
            Class<T> clazz = BeanUtil.getUltimateClass(candidate);
            if (clazz.isArray()) {
                int length = Array.getLength(candidate);
                for (int i = 0; i < length; i++) {
                    Object element = Array.get(candidate, i);
                    Object ultimateElement = getUltimateObject(element);
                    if (element != ultimateElement) {
                        Array.set(candidate, i, ultimateElement);
                    }
                }
            } else if (List.class.isAssignableFrom(clazz)) {
                List list = (List) candidate;
                for (int i = 0; i < list.size(); i++) {
                    Object element = list.get(i);
                    Object ultimateElement = getUltimateObject(element);
                    if (element != ultimateElement) {
                        list.set(i, ultimateElement);
                    }
                }
            } else if (Collection.class.isAssignableFrom(clazz)) {
                Collection<?> collection = (Collection<?>) candidate;
                Collection newCollection = (Collection) ClassUtil.newInstance(clazz);
                for (Object element : collection) {
                    newCollection.add(getUltimateObject(element));
                }
                return (T) newCollection;
            } else if (Map.class.isAssignableFrom(clazz)) {
                Map map = (Map) candidate;
                for (Object key : map.keySet()) {
                    Object value = map.get(key);
                    Object ultimateValue = getUltimateObject(value);
                    if (value != ultimateValue) {
                        map.put(key, ultimateValue);
                    }
                }
            } else if (!clazz.equals(candidate.getClass())) {
                // 如果是代理对象，则取到原始类型，构建原始类型对象，复制属性，形成新的原始对象，用于序列化，以生成正确的类型字段
                try {
                    T newObject = ClassUtil.newInstance(clazz);
                    BeanUtils.copyProperties(candidate, newObject);
                    candidate = newObject;
                } catch (Exception ignored) {
                }
            }
        }
        return candidate;
    }
}
